﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace TCC.WSClient
{
    public class WebServiceInvoker
    {
        private ICollection<ContractDescription> contracts;
        private ServiceEndpointCollection allEndpoints;
        private Dictionary<string, IEnumerable<ServiceEndpoint>> endpointsForContracts;

        /// <summary>
        /// Text description of the available services within this web service.
        /// </summary>
        public List<string> AvailableContracts
        {
            get { return this.contracts.Select(c => c.Name).ToList(); }
        }

        public WebServiceInvoker(Uri webServiceUri)
        {
            if (String.IsNullOrEmpty(webServiceUri.ToString()))
                throw new Exception("Web Service Not Found");

            // Get the metadata file from the service.
            MetadataExchangeClient mexClient = new MetadataExchangeClient(webServiceUri, MetadataExchangeClientMode.HttpGet);
            mexClient.ResolveMetadataReferences = true;
            MetadataSet metaSet = mexClient.GetMetadata();

            // Import all contracts and endpoints
            WsdlImporter importer = new WsdlImporter(metaSet);
            this.contracts = importer.ImportAllContracts();
            this.allEndpoints = importer.ImportAllEndpoints();
        }

        public T InvokeMethod<T>(string contractName, string operationName, params object[] operationParameters)
        {
            CompilerResults results = CompileAssembly();

            // Find the proxy type that was generated for the specified contract
            // (identified by a class that implements 
            // the contract and ICommunicationbject)
            Type clientProxyType = results.CompiledAssembly.GetTypes().First(
                t => t.IsClass &&
                     t.GetInterface(contractName) != null &&
                     t.GetInterface(typeof(ICommunicationObject).Name) != null
            );

            // Get the first service endpoint for the contract
            ServiceEndpoint se = endpointsForContracts[contractName].First();

            // Create an instance of the proxy
            // Pass the endpoint's binding and address as parameters
            // to the ctor
            object instance = results.CompiledAssembly.CreateInstance(
                clientProxyType.Name,
                false,
                System.Reflection.BindingFlags.CreateInstance,
                null,
                new object[] { se.Binding, se.Address },
                CultureInfo.CurrentCulture,
                null
            );

            // Get the operation's method, invoke it, and get the return value
            object retVal = instance.GetType().GetMethod(operationName).Invoke(instance, operationParameters);

            return (T)retVal;
        }

        /// <summary>
        /// Gets a list of all methods available for the specified service.
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        public List<string> EnumerateServiceMethods(string contractName)
        {
            List<string> methods = new List<string>();

            methods = this.contracts.First(c => c.Name == contractName)
                                    .Operations.Select(o => o.Name).ToList();

            return methods;
        }

        private CompilerResults CompileAssembly()
        {
            // Generate type information for each contract
            ServiceContractGenerator generator = new ServiceContractGenerator();
            this.endpointsForContracts = new Dictionary<string, IEnumerable<ServiceEndpoint>>();

            foreach (ContractDescription contract in this.contracts)
            {
                generator.GenerateServiceContractType(contract);
                // Keep a list of each contract's endpoints
                endpointsForContracts[contract.Name] = this.allEndpoints.Where(se => se.Contract.Name == contract.Name).ToList();
            }

            if (generator.Errors.Count != 0)
                throw new Exception("There were errors during code compilation.");

            // Generate a code file for the contracts 
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");

            // Compile the code file to an in-memory assembly
            // Don't forget to add all WCF-related assemblies as references
            CompilerParameters compilerParameters = new CompilerParameters(
                new string[] { "System.dll", "System.ServiceModel.dll", "System.Runtime.Serialization.dll" }
            );
            compilerParameters.GenerateInMemory = true;

            CompilerResults results = codeDomProvider.CompileAssemblyFromDom(compilerParameters, generator.TargetCompileUnit);

            if (results.Errors.Count > 0)
            {
                throw new Exception("There were errors during generated code compilation");
            }

            return results;
        }
    }
}
